import java.awt.Color;
import java.util.ArrayList;

import uchicago.src.reflector.RangePropertyDescriptor;
import uchicago.src.sim.analysis.DataSource;
import uchicago.src.sim.analysis.OpenSequenceGraph;
import uchicago.src.sim.analysis.Sequence;
import uchicago.src.sim.engine.BasicAction;
import uchicago.src.sim.engine.Schedule;
import uchicago.src.sim.engine.SimModelImpl;
import uchicago.src.sim.gui.ColorMap;
import uchicago.src.sim.gui.DisplaySurface;
import uchicago.src.sim.gui.Object2DDisplay;
import uchicago.src.sim.gui.Value2DDisplay;
import uchicago.src.sim.util.SimUtilities;


/**
 * Class that implements the simulation model for the rabbits grass
 * simulation.  This is the first class which needs to be setup in
 * order to run Repast simulation. It manages the entire RePast
 * environment and the simulation.
 *
 * @author Liansheng HUA & Yujie GUO
 */


public class RabbitsGrassSimulationModel extends SimModelImpl {		

	private static final int NUMRABBITS = 10;
	private static final int WORLDXSIZE = 30;
	private static final int WORLDYSIZE = 30;
	private static final int BIRTHTHRESHOLD = 40;
	private static final int GRASSGROWTHRATE = 10;
	private static final int GRASSENERGYLEVEL = 20;

	private static int rabbitMoveEnergy = 2;
	private static int rabbitInitialEnergyLevel = 20;


	private int numRabbits = NUMRABBITS;
	private int birthThreshold = BIRTHTHRESHOLD;
	private int grassGrowthRate = GRASSGROWTHRATE;
	private int worldXSize = WORLDXSIZE;
	private int worldYSize = WORLDYSIZE;
	private int grassEnergyLevel = GRASSENERGYLEVEL;


	private Schedule schedule;

	private RabbitsGrassSimulationSpace space;

	private ArrayList<RabbitsGrassSimulationAgent> agentList;

	private DisplaySurface displaySurf;

	private OpenSequenceGraph populationChart;

	class grassInSpace implements DataSource, Sequence {

		public Object execute() {
			return new Double(getSValue());
		}

		public double getSValue() {
			return (double)space.getTotalGrass();
		}
	}
	
	class rabbitsInSpace implements DataSource, Sequence {

		public Object execute() {
			return new Double(getSValue());
		}

		public double getSValue() {
			return (double)agentList.size();
		}
	}

	public String getName() {
		return "Rabbits Grass Simulation";
	}

	public void begin() {
		// TODO Auto-generated method stub
		buildModel();
		buildSchedule();
		buildDisplay();
		displaySurf.display();
		populationChart.display();
	}


	public void setup() {
		// TODO Auto-generated method stub
		RangePropertyDescriptor dWorldXSize = 
				new RangePropertyDescriptor("WorldXSize", 
					0, 100, 20);
		descriptors.put("WorldXSize", dWorldXSize);

		RangePropertyDescriptor dWorldYSize = 
				new RangePropertyDescriptor("WorldYSize", 
					0, 100, 20);
		descriptors.put("WorldYSize", dWorldYSize);
			
		RangePropertyDescriptor dNumRabbits = 
				new RangePropertyDescriptor("NumRabbits", 
					0, 300, 50);
		descriptors.put("NumRabbits", dNumRabbits);

		RangePropertyDescriptor dBirthThreshold = 
					new RangePropertyDescriptor("BirthThreshold", 
						0, 100, 20);
		descriptors.put("BirthThreshold", dBirthThreshold);

		RangePropertyDescriptor dGrassGrowthRate = 
					new RangePropertyDescriptor("GrassGrowthRate", 
						0, 100, 20);
		descriptors.put("GrassGrowthRate", dGrassGrowthRate );
		
		RangePropertyDescriptor dGrassEnergyLevel = 
					new RangePropertyDescriptor("GrassEnergyLevel", 
						0, 100, 10);
		descriptors.put("GrassEnergyLevel", dGrassEnergyLevel);
	

		space = null;
		agentList = new ArrayList<RabbitsGrassSimulationAgent>();
		schedule = new Schedule(1);

		if (displaySurf != null){
			displaySurf.dispose();
		}
		displaySurf = null;


		if (populationChart != null){
			populationChart.dispose();
		}
		populationChart = null;

		displaySurf = new DisplaySurface(this, "Rabbits Grass Simulation Model Window 1");
		populationChart = new OpenSequenceGraph("Evolution of the rabbits and the grass",this);

		registerDisplaySurface("Rabbits Grass Simulation Model Window 1", displaySurf);
		this.registerMediaProducer("Plot", populationChart);

	}

	public void buildModel(){
		System.out.println("Running BuildModel");

		space = new RabbitsGrassSimulationSpace(worldXSize, worldYSize);
		space.growGrass(grassGrowthRate);

		for (int i = 0; i < numRabbits; i++)
			addNewAgent();

	}

	private void addNewAgent(){
		RabbitsGrassSimulationAgent newRabbitAgent = new RabbitsGrassSimulationAgent(rabbitInitialEnergyLevel);
		agentList.add(newRabbitAgent);
		space.addAgent(newRabbitAgent);
	}


	public void buildDisplay(){
		System.out.println("Running BuildDisplay");

		ColorMap map = new ColorMap();

		for(int i = 1; i<16; i++){
			map.mapColor(i, new Color(0, (int)(i * 8 + 127), 0));
		}
		map.mapColor(0, Color.black);

		Value2DDisplay displayGrass = new Value2DDisplay(space.getCurrentGrassSpace(), map);
		Object2DDisplay displayRabbit = new Object2DDisplay(space.getCurrentRabbitSpace());
		displayRabbit.setObjectList(agentList);
		displaySurf.addDisplayableProbeable(displayGrass, "Grass");
		displaySurf.addDisplayableProbeable(displayRabbit, "Rabbits");
		
		populationChart.addSequence("Grass", new grassInSpace());
		populationChart.addSequence("Rabbits", new rabbitsInSpace());

	}

	public void buildSchedule(){
		System.out.println("Running BuildSchedule");

		class RabbitsGrassSimuStep extends BasicAction {
			public void execute() {
				SimUtilities.shuffle(agentList);
				int k = agentList.size();
				for(int i =0; i < k; i++){
					RabbitsGrassSimulationAgent agent = (RabbitsGrassSimulationAgent)agentList.get(i);
					agent.eatGrass(grassEnergyLevel);
					reproduce(agent);
					agent.move(rabbitMoveEnergy);
				}
				removeDeadRabbits();
				space.growGrass(grassGrowthRate);
				displaySurf.updateDisplay();
			}
		}

		schedule.scheduleActionBeginning(0, new RabbitsGrassSimuStep());

		class PopulationPlot extends BasicAction {
			public void execute(){
				populationChart.step();
			}
		}

		schedule.scheduleActionAtInterval(10, new PopulationPlot());

	}

	private void reproduce(RabbitsGrassSimulationAgent agent) {
		if (agent.getEnergyLevel() >= birthThreshold) {
			addNewAgent();
			agent.setEnergyLevel(agent.getEnergyLevel()-rabbitInitialEnergyLevel);
		}
	}


	private void removeDeadRabbits(){
		for(int i = (agentList.size() - 1); i >= 0 ; i--){
			RabbitsGrassSimulationAgent agent = (RabbitsGrassSimulationAgent)agentList.get(i);
			if(agent.getEnergyLevel() <= 0){
				space.removeAgentAt(agent.getX(), agent.getY());
				agentList.remove(i);
			}
		}
	}

	public String[] getInitParam() {
		String[] initParams = { "WorldXSize", "NumRabbits", "WorldYSize", "BirthThreshold", "GrassGrowthRate", "GrassEnergyLevel"};
		return initParams;
	}

	public Schedule getSchedule() {
		return schedule;
	}

	public int getWorldXSize() {
		return worldXSize;
	}

	public void setWorldXSize(int wxs){
		worldXSize = wxs;
	}

	public int getWorldYSize() {
		return worldYSize;
	}

	public void setWorldYSize(int wys){
		worldYSize = wys;
	}

	public int getNumRabbits() {
		return numRabbits;
	}

	public void setNumRabbits(int nr){
		numRabbits = nr;
	}

	public int getBirthThreshold() {
		return birthThreshold;
	}

	public void setBirthThreshold(int bt){
		birthThreshold = bt;
	}

	public int getGrassGrowthRate() {
		return grassGrowthRate;
	}

	public void setGrassGrowthRate(int ggr){
		grassGrowthRate = ggr;
	}

	public int getGrassEnergyLevel() {
		return grassEnergyLevel;
	}

	public void setGrassEnergyLevel(int energy){
		grassEnergyLevel = energy;
	}


}




